package maple.core;

import java.text.ParseException;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.Vector;

import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;

import maple.lang.Command;
import maple.lang.Language;
import maple.util.Planner;
import maple.util.Profile;
import maple.util.Sensor;
import maple.util.Service;
import maple.util.Task;
import maple.util.tasks.MasterTask;
import maple.util.tasks.GatewayTask;
import maple.util.Timestamp;
import maple.util.Tuple;

public class Master extends Producer
{
	private final long TIMEOUT = 60000;

	protected HashMap< String, Tuple > _perceptions;
	
	protected HashMap< String, MasterTask > _tasks;
	protected HashMap< String, Vector< MasterTask > > _subtasks;

	public Master()
	{
		super();

		// from Producer
		_names.add( Language.Instruction.SELECT.toString() );
		_names.add( Language.Instruction.SET.toString() );
		_types.add( Sensor.Type.MASTER.toString() );
		_types.add( Sensor.Type.MASTER.toString() );

		_perceptions = new HashMap< String, Tuple >();

		_tasks = new HashMap< String, MasterTask >();
		_subtasks = new HashMap< String, Vector< MasterTask > >();
	}

	protected void actionInform( final ACLMessage message )
	{
		//_logger.info( message.getSender().getLocalName() + Language.DELIMITER + message.getContent() );
		System.out.println( Timestamp.Default.format( new Date( System.currentTimeMillis() ) ) + Language.DELIMITER + message.getSender().getLocalName() + Language.DELIMITER + message.getContent() );

		try
		{
			String[] tokens = message.getContent().split( Language.DELIMITER );
			if( tokens.length == 2 )
			{
				String postcondition = message.getUserDefinedParameter( Planner.Property.POSTCONDITION.toString() );
				String location = message.getUserDefinedParameter( Profile.Property.LOCATION.toString() );
				String type = location + Language.DELIMITER + postcondition;
				Tuple tuple = new Tuple( Timestamp.Default.parse( tokens[ 0 ] ).getTime(), type, tokens[ 1 ] );
				_perceptions.put( type, tuple );
			}
		}
		catch( NumberFormatException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
		catch( ParseException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
	}

	protected void actionRequest( final ACLMessage message )
	{
		_logger.info( message.getSender().getLocalName() + Language.DELIMITER + message.getContent() );
		System.out.println( Timestamp.Default.format( new Date( System.currentTimeMillis() ) ) + Language.DELIMITER + message.getSender().getLocalName() + Language.DELIMITER + message.getContent() );
		
		String sender = message.getSender().getName();
		String content = message.getContent();
		String receiver = message.getReplyWith();
		if( receiver == null )
		{
			receiver = sender;
		}
		
		String result = Command.Message.BAD_COMMAND.toString();
		Command command = new Command( content );
		if( command != null && command.isAvailable() )
		{
			MasterTask task = new MasterTask( receiver, command, getName() );
			String key = task.toString();

			switch( command.getInstruction() )
			{
				case SELECT:
					result = select( command );
					break;
				case SET:
					if( task.isAvailable() && _tasks.containsKey( key ) )
					{
						result = undo( _tasks.get( key ) ).toString();
					}
					else
					{
						result = set( task ).toString();
					}
					break;
			}

			sendMessage( ACLMessage.INFORM, sender, result, getParameters( task ), null );
		}
		else
		{
			HashMap< String, String > parameters = new HashMap< String, String >();
			parameters.put( Task.Property.COMMAND.toString(), command.toString() );
			sendMessage( ACLMessage.INFORM, sender, result.toString(), parameters, null );
		}
	}
	
	protected void check()
	{
		Vector< String > removes = new Vector< String >();
		
		Vector< MasterTask > tasks = new Vector< MasterTask >( _tasks.values() );
		for( MasterTask task : tasks )
		{
			if( exist( task.getSender() ) )
			{
				Long interval = Long.parseLong( task.getProperty( MasterTask.Property.INTERVAL.toString() ) );
				if( interval <= 0 )
				{
					if( execute( true, task ) )
					{
						removes.add( task.toString() );
					}
				}
				else
				{
					if( !task.isDispatched() )
					{
						while( !task.isFinished() )
						{
							Command command = new Command( task.getPlan() );
							String receiver = task.getPlanReceiver();
							String content = command.toString();
							String subtask = receiver + Language.DELIMITER + content;
							switch( command.getInstruction() )
							{
								case READ: case WRITE:
									if( _subtasks.containsKey( subtask ) )
									{
										Vector< MasterTask > list = _subtasks.get( subtask );
										list.add( task );
										_subtasks.put( subtask, list );
									}
									else
									{
										Vector< MasterTask > list = new Vector< MasterTask >();
										list.add( task );
										_subtasks.put( subtask, list );
										sendMessage( ACLMessage.REQUEST, receiver, content, getParameters( task ), null );
									}
									break;
							}
							task.setState( task.getState() + 1 );
						}
						task.setDispatched();
						task.setState( 0 );
					}

					// execute the routines
					if( ( System.currentTimeMillis() - task.getTriggerTime() ) >= interval )
					{
						execute( false, task );
					}
				}
			}
			else
			{
				undo( task );
				removes.add( task.toString() );
			}
		}

		// remove the finished tasks
		for( String task : removes )
		{
			_tasks.remove( task );
		}
	}

	/**
	 * return true if the specified is finished
	 *
	 * @param	immediate	true if the specified task is not a routine
	 * @param	task		the specified task
	 * @return	true if the specified is finished
	 *
	 * @author	snowfoxy
	 * @since	2011-11-26
	 */
	private boolean execute( final boolean immediate, final MasterTask task )
	{
		long current = System.currentTimeMillis();

		boolean flag = true;
		while( flag )
		{
			if( task.isFinished() )
			{
				flag = false;

				String perception = task.getProperty( MasterTask.Property.LOCATION.toString() ) + Language.DELIMITER + task.getProperty( MasterTask.Property.GOAL.toString() );
				if( _perceptions.containsKey( perception ) )
				{
					Tuple tuple = _perceptions.get( perception );
					long timestamp = tuple.getTimestamp();
					if( ( current - timestamp ) < TIMEOUT )
					{
						task.setTriggerTime( timestamp );
						task.setState( 0 );
						sendMessage( ACLMessage.INFORM, task.getSender(), Timestamp.Default.format( new Date( timestamp ) ) + Language.DELIMITER + tuple.getValue(), getParameters( task ), null );  

						if( immediate )
						{
							return true;
						}
					}
				}
			}
			else
			{
				String receiver = task.getPlanReceiver();
				Long interval = Long.parseLong( task.getProperty( MasterTask.Property.INTERVAL.toString() ) );
				if( interval <= 0 )
				{
					interval = TIMEOUT;
				}
				
				Command command = new Command( task.getPlan() );
				switch( command.getInstruction() )
				{
					case READ: case WRITE:
						if( immediate )
						{
							sendMessage( ACLMessage.REQUEST, receiver, command.toString(), getParameters( task ), null ); 
						}

						task.setState( task.getState() + 1 );
						break;
					case INTERPRET:
						for( String parameter : command.getParameters() )
						{
							String perception = task.getProperty( MasterTask.Property.LOCATION.toString() ) + Language.DELIMITER + parameter;
							if( _perceptions.containsKey( perception ) )
							{
								Tuple tuple = _perceptions.get( perception );
								if( ( current - tuple.getTimestamp() ) < interval )
								{
									command.setParameter( parameter, tuple.getValue() );
								}
								else
								{
									flag = false;
								}
							}
							else
							{
								flag = false;
							}
						}

						if( flag )
						{
							sendMessage( ACLMessage.REQUEST, receiver, command.toString(), getParameters( task ), null ); 
							task.setState( task.getState() + 1 );
						}
						break;
				}
			}
		}
		return false;
	}

	/**
	 * return the specified parameters for sending the message or null if the task is not available
	 *
	 * @param	task	the specified task
	 * @return	the specified parameters for sending the message
	 *
	 * @author	snowfoxy
	 * @since	2011-11-16
	 */
	protected HashMap< String, String > getParameters( final MasterTask task )
	{
		if( task.isAvailable() )
		{
			HashMap< String, String > parameters = new HashMap< String, String >();
			parameters.put( Task.Property.COMMAND.toString(), task.getCommand() );
			parameters.put( Planner.Property.POSTCONDITION.toString(), task.getProperty( MasterTask.Property.GOAL.toString() ) );
			parameters.put( Profile.Property.LOCATION.toString(), task.getProperty( MasterTask.Property.LOCATION.toString() ) );
			return parameters;
		}
		return null;
	}

	/**
	 * return the result of the select command with the specified command
	 *
	 * @param	command	the specified command
	 * @return	the result of the select command with the specified command 
	 *
	 * @author	snowfoxy
	 * @since	2011-11-16
	 */
	protected String select( final Command command )
	{
		String[] tokens = getName().split( Language.AT );
		DFAgentDescription[] candidates = listAllAgents( tokens[ 1 ], command.toService() );
		if( candidates == null )
		{
			return Command.Message.EXCEPTION_FIPA.toString();
		}
		else if( candidates.length > 0 )
		{
			String result = "";

			// select the matched services
			for( DFAgentDescription candidate : candidates )
			{
				Iterator services = candidate.getAllServices();
				while( services.hasNext() )
				{
					ServiceDescription service = ( ServiceDescription ) services.next();
					if( command.match( service ) )
					{
						result = result + candidate.getName().getName()
								 + Language.DELIMITER
								 + Service.toString( service ) + "\n";
					}
				}
			}
			return result.substring( 0, result.length() - 1 );
		}
		return Command.Message.NOT_FOUND.toString();
	}

	/**
	 * return the status of the set command with the specified task
	 *
	 * @param	task	the specified task
	 * @return	the status of the set command with the specified task
	 *
	 * @author	snowfoxy
	 * @since	2011-11-16
	 */
	protected Command.Message set( final MasterTask task )
	{
		if( task.isAvailable() )
		{
			String goal = task.getProperty( MasterTask.Property.GOAL.toString() );
			String interval = task.getProperty( MasterTask.Property.INTERVAL.toString() );
			String fact = MasterTask.Property.LOCATION.toString()
						  + Language.ASSIGN
						  + Language.QUOTE
						  + task.getProperty( MasterTask.Property.LOCATION.toString() )
						  + Language.QUOTE;

			String[] tokens = getName().split( Language.AT );
			HashMap< ServiceDescription, String > services = listAllServices( tokens[ 1 ] );
			if( services != null )
			{
				Vector< ServiceDescription > keys = new Vector< ServiceDescription >( services.keySet() );
				Vector< String > plans = Planner.plan( fact, Service.toOperators( keys ), goal, interval, getName() );
				if( plans == null || plans.size() == 0 )
				{
					return Command.Message.NOT_FOUND;
				}
				task.setPlans( plans );
				_tasks.put( task.toString(), task );	
				return Command.Message.OK;
			}
			return Command.Message.EXCEPTION_FIPA;
		}
		return Command.Message.BAD_COMMAND;
	}

	/**
	 * return the status of removing the specified task
	 *
	 * @param	task	the specified task
	 * @return	the status of removing the specified task
	 *
	 * @author	snowfoxy
	 * @since	2011-11-16
	 */
	protected Command.Message undo( final MasterTask task )
	{
		if( task.isDispatched() )
		{
			task.setState( 0 );

			for( String plan : task.getPlans() )
			{
				Command command = new Command( plan );
				String receiver = task.getPlanReceiver();
				String content = command.toString();
				String subtask = receiver + Language.DELIMITER + content;
				switch( command.getInstruction() )
				{
					case READ: case WRITE:
						if( _subtasks.containsKey( subtask ) )
						{
							Vector< MasterTask > list = _subtasks.get( subtask );
							list.remove( task );
							if( list.size() == 0 )
							{
								_subtasks.remove( subtask );
								sendMessage( ACLMessage.REQUEST, task.getPlanReceiver(), command.toString(), getParameters( task ), null ); 
							}
						}
						break;
				}
				task.setState( task.getState() + 1 );
			}
		}
		_tasks.remove( task.toString() );
		return Command.Message.OK;
	}
}
